package randombeanmachine;

import java.util.*;
import java.util.logging.Logger;
import java.lang.reflect.*;

public class RandomBeanMachine {
    public RandomBeanMachine withOneEntryCollections() {
        factories.put(Collection.class, new OneEntryListValueFactory());
        factories.put(List.class, new OneEntryListValueFactory());
        factories.put(Set.class, new OneEntrySetValueFactory());
        factories.put(Map.class, new OneEntryMapValueFactory());
        return this;
    }

    class OneEntryMapValueFactory extends DefaultBeanValueFactory {

        public Object create(Field field) {
            Map map = new HashMap();
            Class keyType = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
            Class valueType = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[1];
            map.put(new RandomBeanMachine().create(keyType), new RandomBeanMachine().create(valueType));
            return map;
        }

        public Object create(String name) {
            throw new UnsupportedOperationException(name + " does not support create via a name.");
        }
    }

    class OneEntryListValueFactory extends DefaultBeanValueFactory {

        public Object create(Field field) {
            ArrayList oneEntryList = new ArrayList();
            Class type = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
            oneEntryList.add(new RandomBeanMachine().create(type));
            return oneEntryList;
        }

        public Object create(String name) {
            throw new UnsupportedOperationException(name + " does not support create via a name.");
        }
    }

    class OneEntrySetValueFactory extends DefaultBeanValueFactory {

        public Object create(Field field) {
            Set set = new HashSet();
            Class type = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
            set.add(new RandomBeanMachine().create(type));
            return set;
        }

        public Object create(String name) {
            throw new UnsupportedOperationException(name + " does not support create via a name.");
        }
    }

    class BlankMapValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return new HashMap();
        }

        BlankMapValueFactory() {
            super();
        }
    }

    class BlankSetValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return new HashSet();
        }

        BlankSetValueFactory() {
            super();
        }
    }

    class BlankListValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return new ArrayList();
        }

        BlankListValueFactory() {
            super();
        }
    }

    class EmptyMapValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return Collections.EMPTY_MAP;
        }

        EmptyMapValueFactory() {
            super();
        }
    }

    class EmptySetValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return Collections.EMPTY_SET;
        }

        EmptySetValueFactory() {
            super();
        }
    }

    class EmptyListValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return Collections.EMPTY_LIST;
        }

        EmptyListValueFactory() {
            super();
        }
    }

    class NullValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return null;
        }

        NullValueFactory() {
            super();
        }
    }

    class ByteArrayValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return new byte[0];
        }

        ByteArrayValueFactory() {
            super();
        }
    }

    class ByteValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return new Byte((byte) 1);
        }

        ByteValueFactory() {
            super();
        }
    }

    class IntegerValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return new Integer(Random.between(0, 5).nextInt());
        }

        IntegerValueFactory() {
            super();
        }
    }

    class BooleanValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return Boolean.valueOf(random().nextBoolean());
        }

        BooleanValueFactory() {
            super();
        }
    }

    public class NormalisedDateValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return random().nextNormalisedDate();
        }

        public NormalisedDateValueFactory() {
        }
    }

    class DateValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {

            return random().nextRandomDate();
        }

        DateValueFactory() {
            super();
        }
    }

    class DoubleValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return new Double(random().nextDouble());
        }


        DoubleValueFactory() {
            super();
        }
    }

    class LongValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return new Long(RandomBeanMachine.randomBeanCounter++);
        }

        LongValueFactory() {
            super();
        }
    }

    public class CharacterArrayValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return Random.characters(5);

        }

        public CharacterArrayValueFactory() {
            super();
        }
    }

    public class CharacterValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return random().nextCharacter();
        }

        public CharacterValueFactory() {
            super();
        }
    }

    public class StringArrayValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            String randomStrings[] = random().nextStrings(5);
            return randomStrings;
        }

        public StringArrayValueFactory() {
            super();
        }
    }

    public class StringValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return identifier + Random.between(1000, 10000).nextInt();
        }

        public StringValueFactory() {
            super();
        }
    }

    public class ObjectValueFactory
            extends DefaultBeanValueFactory {

        public Object create(String identifier) {
            return new Object();
        }

        public ObjectValueFactory() {
            super();
        }
    }

    public static interface RandomBeanValueFactory {
        Object create(Field field);

        Object create(String name);
    }

    public static abstract class DefaultBeanValueFactory implements RandomBeanValueFactory {


        public Object create(Field field) {
            return create(field.getName());
        }

        public abstract Object create(String name);
    }


    private Logger log;
    private static long randomBeanCounter = Random.nextLong();
    private Map<Class, RandomBeanValueFactory> factories;
    static Class class$com$bnpparibas$risk$credit$testtools$beanmachine$RandomBeanMachine; /* synthetic field */

    public RandomBeanMachine() {
        factories = new HashMap<Class, RandomBeanValueFactory>();
        factories.put(Object.class, new StringValueFactory());
        factories.put(String.class, new StringValueFactory());
        factories.put(String[].class, new StringArrayValueFactory());
        factories.put(Character.class, new CharacterValueFactory());
        factories.put(Character[].class, new CharacterArrayValueFactory());
        factories.put(Character.TYPE, new CharacterArrayValueFactory());
        factories.put(char[].class, new CharacterArrayValueFactory());
        factories.put(Long.class, new LongValueFactory());
        factories.put(Long.TYPE, new LongValueFactory());
        factories.put(Double.class, new DoubleValueFactory());
        factories.put(Double.TYPE, new DoubleValueFactory());
        factories.put(Date.class, new DateValueFactory());
        factories.put(Boolean.class, new BooleanValueFactory());
        factories.put(Boolean.TYPE, new BooleanValueFactory());
        factories.put(Integer.class, new IntegerValueFactory());
        factories.put(Integer.TYPE, new IntegerValueFactory());
        factories.put(Byte.TYPE, new ByteValueFactory());
        factories.put(byte[].class, new ByteArrayValueFactory());
        factories.put(Byte.class, new ByteValueFactory());
        factories.put(Collection.class, new EmptyListValueFactory());
        factories.put(List.class, new EmptyListValueFactory());
        factories.put(Set.class, new EmptySetValueFactory());
        factories.put(Map.class, new EmptyMapValueFactory());
    }

    public RandomBeanMachine withDefaultBlankCollections() {
        factories.put(Collection.class, new BlankListValueFactory());
        factories.put(List.class, new BlankListValueFactory());
        factories.put(Set.class, new BlankSetValueFactory());
        factories.put(Map.class, new BlankMapValueFactory());
        return this;
    }

    public RandomBeanMachine registerValueFactory(Class aClass, RandomBeanValueFactory valueFactory) {
        for (Class classHierarchy = valueFactory.create(getClass().getDeclaredFields()[0]).getClass(); !classHierarchy.equals(Object.class); classHierarchy = classHierarchy.getSuperclass())
            if (implementsOrExtends(aClass, classHierarchy))
                return registerUnprotectedValueFactory(aClass, valueFactory);

        throw new RandomBeanMachineException("Cannot register ValueFactory : " + valueFactory + " for class : " + aClass.getName() + ", as it does not produce objects of this class type.");
    }

    private RandomBeanMachine registerUnprotectedValueFactory(Class aClass, RandomBeanValueFactory valueFactory) {
        factories.remove(aClass);
        factories.put(aClass, valueFactory);
        return this;
    }

    public RandomBeanMachine registerNullValueFactory(Class aClass) {
        return registerUnprotectedValueFactory(aClass, new NullValueFactory());
    }

    private boolean implementsOrExtends(Class aClass, Class classHierarchy) {
        Collection<Class> interfaces = Arrays.asList(classHierarchy.getInterfaces());
        return classHierarchy.equals(aClass) || interfaces.contains(aClass);
    }

    public <T> T create(Class<T> aClass) {
        if(factories.get(aClass) != null) {
            return (T) factories.get(aClass).create("something");
        }


        Object object;
        Constructor declaredConstructors[] = aClass.getDeclaredConstructors();
        if (declaredConstructors.length == 0) {
            if (isUserDefined(aClass)) {
                throw new RandomBeanMachineException("Could not construct class " + aClass.getName() + " as it does not have any declared constructors.");
            } else {
                object = new Object();
            }
        } else {
            Constructor constructor = findSimplestConstructor(declaredConstructors);
            constructor.setAccessible(true);
            Class parameterTypes[] = constructor.getParameterTypes();
            Object ctorParams[] = new Object[parameterTypes.length];
            for (int i = 0; i < ctorParams.length; i++) {
                ctorParams[i] = createConstructorParameter(parameterTypes[i]);
            }
            try {
                object = constructor.newInstance(ctorParams);
            }
            catch (InstantiationException e) {
                throw new RandomBeanMachineException("Could not construct class : " + aClass.getName(), e);
            } catch (InvocationTargetException e) {
                throw new RandomBeanMachineException("Could not construct class : " + aClass.getName(), e);
            } catch (IllegalAccessException e) {
                throw new RandomBeanMachineException("Could not construct class : " + aClass.getName(), e);
            }
        }
        try {
            while (isUserDefined(aClass)) {
                setValuesOnFieldsForClass(aClass, object);
                aClass = (Class<T>) aClass.getSuperclass();
            }
            return (T) object;

        } catch (IllegalAccessException e) {
            throw new RandomBeanMachineException("Could not construct class : " + aClass.getName(), e);
        }
    }

    private boolean isUserDefined(Class aClass) {
        if (aClass == Object.class) {
            return false;
        }

        return true;
    }

    private Constructor findSimplestConstructor
            (Constructor
                    declaredConstructors[]) {
        Constructor simplestConstructor = declaredConstructors[0];
        for (int i = 1; i < declaredConstructors.length; i++) {
            Constructor declaredConstructor = declaredConstructors[i];
            if (declaredConstructor.getParameterTypes().length < simplestConstructor.getParameterTypes().length)
                simplestConstructor = declaredConstructor;
        }

        return simplestConstructor;
    }

    private Object createConstructorParameter(Class parameterType) {
        RandomBeanValueFactory valueFactory = (RandomBeanValueFactory) factories.get(parameterType);
        return valueFactory != null ? valueFactory.create(parameterType.getName()) : null;
    }

    private void setValuesOnFieldsForClass(Class aClass, Object object) throws IllegalAccessException {
        Field fields[] = aClass.getDeclaredFields();
        for (Field field : fields) {
            setFieldValue(field, object);
        }
    }

    private void setFieldValue(Field field, Object object) throws IllegalAccessException {
        field.setAccessible(true);
        if (!Modifier.isStatic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers())) {
            RandomBeanValueFactory valueFactory = (RandomBeanValueFactory) factories.get(field.getType());
            try {
                field.set(object, createValueForField(valueFactory, field));
            }
            catch (IllegalArgumentException e) {
                throw new RandomBeanMachineException("Could not set " + field.getName(), e);
            }
        }
    }

    private Object createValueForField(RandomBeanValueFactory valueFactory, Field field) {
        return valueFactory != null ? valueFactory.create(field) : create(field.getType());
    }

    public void populate(Object objects[], Class classes[]) {
        for (int i = 0; i < objects.length; i++)
            objects[i] = create(classes[Random.between(0, 5).nextInt()]);

    }

    public void populate(Object newDeals[]) {
        Class componentType = ((Object) (newDeals)).getClass().getComponentType();
        for (int i = 0; i < newDeals.length; i++)
            newDeals[i] = create(componentType);

    }

    public void populate(Object objects[], Class aClass) {
        populate(objects, new Class[]{aClass});
    }

    private Random random() {
        return new Random();
    }


}

